using UnityEngine;

namespace ai_library
{
	public class StateMachineException : System.Exception
	{
		protected string message;
		public StateMachineException(string message) { this.message = message; }
		public override string Message { get { return message; } }
		public override string ToString() { return "State Machine Exception: " + message; }
	}

	public class StateMachine<T> : IStateMachine<T>
	{
		private IState<T> m_currentState = null;
		private IState<T> m_previousState = null;
		private IState<T> m_globalState = null;

		private T m_owner;

		public StateMachine(T t)
		{
			m_owner = t;
		}

		public IState<T> CurrentState
		{
			get
			{
				return m_currentState;
			}
		}

		public IState<T> GlobalState 
		{
			get
			{
				return m_globalState;
			}
		}

		private bool ChangeState(ref IState<T> currentState, IState<T> targetState)
		{
			IState<T> previousState = null;
			return ChangeState(ref currentState, targetState, ref previousState);
		}
		
		private bool ChangeState(ref IState<T> currentState, IState<T> targetState, ref IState<T> previousState)
		{
			try
			{
				if (DoesStateExist(targetState))
				{
					if (DoesStateExist(currentState))
					{
						currentState.Exit(m_owner);
						previousState = currentState;
					}
					
					currentState = targetState;
					currentState.Enter(m_owner);
					
					return true;
				}
			}
			catch(StateException e)
			{
				throw new StateMachineException(e.ToString());
			}
			
			return false;
		}

		private bool DoesStateExist(IState<T> state)
		{
			return state != null;
		}

		public bool ChangeCurrentState(IState<T> state)
		{
			return ChangeState(ref m_currentState, state, ref m_previousState);
		}
		
		public bool ChangeGlobalState(IState<T> state)
		{
			return ChangeState(ref m_globalState, state);
		}

		public bool HasCurrentState()
		{
			return DoesStateExist(m_currentState);
		}
		
		public bool HasGlobalState()
		{
			return DoesStateExist(m_globalState);
		}

		public bool RevertCurrentState()
		{
			if (DoesStateExist(m_previousState))
			{
				ChangeState(ref m_currentState, m_previousState);
				m_previousState = null;

				return true;
			}

			return false;
		}

		public bool ProcessMessage(Message m)
		{
			if (DoesStateExist(m_globalState)
			    && m_globalState.OnMessage(m_owner, m))
				return true;
			
			if (DoesStateExist(m_currentState)
			    && m_currentState.OnMessage(m_owner, m))
				return true;

			return false;
		}

		public void Update(float dt)
		{
			try
			{
				if (DoesStateExist(m_globalState))
					m_globalState.Update (m_owner, dt);
				
				if (DoesStateExist(m_currentState))
					m_currentState.Update(m_owner, dt);
			}
			catch(StateException e)
			{
				throw new StateMachineException(e.ToString());
			}
		}
	}
}
